﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Accord.Audio;
using TimbreRecognition.Extraction;
using TimbreRecognition.FeatureExtraction;
using TimbreRecognition.Recognition;
using TimbreRecognition.Recognition.Model;
using TimbreRecognition.Util;

namespace TimbreRecognition.Desktop
{
    public partial class RecognitionForm : Form
    {
        private Color[] colors =
        {
            Color.Red,
            Color.Green,
            Color.Blue, 
            Color.Violet, 
            Color.Orange, 
            Color.Yellow, 
            Color.LawnGreen,
            Color.Aqua,
            Color.BlueViolet,
            Color.CadetBlue,
            Color.Olive,
            Color.SpringGreen,
            Color.Purple,
            Color.Brown,
            Color.DeepPink
        };

        private const int NumberOfPointsOnChart = 10000;

        private const int SecondsPerMinute = 60;

        private readonly INetwork network;

        private readonly string[] instruments;

        private List<double[]> results;

        public RecognitionForm()
        {
            InitializeComponent();
        }

        public RecognitionForm(INetwork network, string[] instruments)
            : this()
        {
            this.network = network;
            this.instruments = instruments;

            this.fromFileRadioButton.Checked = true;
        }

        private void fromFileRadioButton_CheckedChanged(object sender, EventArgs e)
        {
            changeMode(fromFileRadioButton.Checked);
        }

        private void changeMode(bool isFileMode)
        {
            this.filePathTextBox.Enabled = isFileMode;
            this.browseFileButton.Enabled = isFileMode;

            this.deviceComboBox.Enabled = !isFileMode;

            this.realTimeCheckBox.Enabled = isFileMode;
        }

        private void browseButton_Click(object sender, EventArgs e)
        {
            DialogResult result = openFileDialog.ShowDialog();
            if (result == DialogResult.OK)
            {
                this.filePathTextBox.Text = openFileDialog.FileName;
            }
        }

        private void recognizeButton_Click(object sender, EventArgs e)
        {
            if (this.fromFileRadioButton.Checked)
            {
                RecognizeFromFile();
            }
            else
            {
                RecognizeFromDevice();
            }
        }

        private void RecognizeFromFile()
        {
            string fileName = this.openFileDialog.FileName;
            SignalData signalData = WaveUtil.GetSignalData(fileName);

            UpdateChart(signalData.Data);

            FeatureExtractionFacade facade = new FeatureExtractionFacade(instruments);
            List<DataItem> features = facade.ExtractFromFile(fileName);

            NeuronNetworkWorker networkHelper = new NeuronNetworkWorker(network);
            results = networkHelper.ClassifyBatch(features.Select(o=>o.DataSeries));

            ShowResult(results);
        }

        private void ShowResult(List<double[]> results)
        {
            Graphics graphics = this.resultPanel.CreateGraphics();
            graphics.Clear(this.resultPanel.BackColor);

            int classCount = results[0].Length;

            float lineGap = (float)resultPanel.Height / classCount / 4;

            float regtangleWidth = (float)resultPanel.Width / results.Count;
            float regtangleHeight = (float) (resultPanel.Height - lineGap * classCount) / classCount;

            if (colors.Length < classCount)
            {
                throw new ArgumentException("Insufficient number of brushes");
            }

            for (int i = 0; i < results.Count; i++)
            {
                double[] result = results[i];
                int classIndex = GetClassIndex(result);                 

                Brush brush = new SolidBrush(colors[classIndex]);

                graphics.FillRectangle(brush, i * regtangleWidth, classIndex * (regtangleHeight + lineGap),
                    regtangleWidth, regtangleHeight);  
            }           
        }

        private void ShowLegend()
        {
            Graphics legendGraphics = this.legendPanel.CreateGraphics();
            Font font = new Font(SystemFonts.DialogFont.FontFamily, 10);

            int gap = this.legendPanel.Height / instruments.Length / 3;
            int regtangleSize = (this.legendPanel.Height - gap * (instruments.Length + 1)) / instruments.Length;

            for (int i = 0; i < instruments.Length; i++)
            {
                Brush brush = new SolidBrush(colors[i]);
                string instrument = instruments[i];

                int y = i * (regtangleSize + gap) + gap;
                legendGraphics.FillRectangle(brush, gap, y, regtangleSize, regtangleSize);
                legendGraphics.DrawString(instrument, font, Brushes.Black, 2 * gap + regtangleSize, y);
            }
        }

        private int GetClassIndex(double[] result)
        {
            int index = -1;
            for (int i = 0; i < result.Length; i++)
            {
                double value = result[i];
                if (value == 1)
                {
                    if (index == 1)
                    {
                        throw new ArgumentException("Multiple class");
                    }

                    index = i;
                }
            }

            if (index == -1)
            {
                throw new ArgumentException("Class is not determined");
            }

            return index;
        }

        private void RecognizeFromDevice()
        {

        }

        private void UpdateChart(float[] data)
        {
            this.chart.Series[0].Points.Clear();

            float[] approximated = DataUtil.ApproximateArray(data, NumberOfPointsOnChart);

            for (int i = 0; i < approximated.Length; i++)
            {
                this.chart.Series[0].Points.AddXY(i + 1, approximated[i]);
            }
        }

        private void resultPanel_Paint(object sender, PaintEventArgs e)
        {
            ShowLegend();
            if (results != null)
            {
                ShowResult(results);
            }
        }

        private int GetDisplayedLengthInSeconds()
        {
            return (int) (this.MinuteUpDown.Value * SecondsPerMinute + this.SecondUpDown.Value);
        }
    }
}
